import datetime
import logging
import os.path
from sklearn.metrics import accuracy_score
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
from sklearn.model_selection import StratifiedKFold
from sklearn.feature_selection import RFECV
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from util.特征处理 import CustomFeatureProcessor

from xgboost import XGBClassifier
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns  # 画图包
import copy
from util.log_util import Logger
# from util.log_util import Logger.get
from xgboost import XGBRegressor
from sklearn.preprocessing import OneHotEncoder
# 导库
from sklearn.model_selection import train_test_split  # 划分数据集
from sklearn.linear_model import LogisticRegression  # 逻辑回归
from sklearn.preprocessing import StandardScaler  # 交叉验证，网格搜索
from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score, mean_squared_error, \
    mean_absolute_error
from sklearn.model_selection import GridSearchCV
import joblib
# 特征处理和转换
from sklearn.preprocessing import LabelEncoder, StandardScaler, MinMaxScaler
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier
from imblearn.over_sampling import SMOTE

# 设置绘图风格
plt.rcParams['font.family'] = 'SimHei'
plt.rcParams['font.size'] = 15


def analysis_data(data):
    """
    1.查看数据
    2.处理数据
    结论：
        月收入在5000以下的人才流失度比较大
        年龄在30到40之间人才流失度比较大
        结论受教育程度是大学的人才流失度比较大
        # 结论：家庭距离越远的离职人数较多，而家庭距离较近的离职人数较少。
        # 环境满意度基本和离职率没有关系。
        # 工作投入越低的离职人数较多，而工作投入较高的离职人数较少。
        # 职位等级越低的离职人数较多，而职位等级的离职人数较少。
        #关系满意度基本和离职率没有关系
        #股权等级越低离职率越高
        #总工作年限越低离职率越高，
        #上一年培训次数基本和离职率没有关系
    :param data:
    :return:
    """
    data = pd.read_csv('../data/train.csv')
    # 绘制人才流失图
    plt.figure(figsize=(8, 8))
    # plt.pie([922, 178],
    #         labels=['Stay(0)', 'Leave(1)'],
    #         autopct='%1.1f%%',
    #         colors=sns.color_palette('pastel'))
    # plt.title('人才流失情况')
    # plt.savefig('../data/fig/人才流失情况图.png')
    # # 检查空值
    missing_values = data.isna().sum()
    # 查看数据类型是字符串的特征
    for column in data.columns:
        if data[column].dtype == object:
            print(f'{column} : {data[column].unique()}')
    # 查看数据类型是数字的特征，以及数据范围
    for column in data.columns:
        if data[column].dtype != object:
            print(f'{column} : {data[column].min()} : {data[column].max()}')
    # # #绘制月收入情况图
    # sns.kdeplot(data=data[data['Attrition']==1],x='MonthlyIncome',label='Attr')
    # sns.kdeplot(data=data[data['Attrition']==0],x='MonthlyIncome',label='not')
    # plt.grid(True)
    # plt.legend(loc='best')
    # plt.savefig('../data/fig/月收入人才流失图')
    ###结论月收入在5000以下的人才流失度比较大
    # 绘制年龄情况图
    # sns.kdeplot(data=data[data['Attrition'] == 1], x='Age', label='Attr')
    # sns.kdeplot(data=data[data['Attrition'] == 0], x='Age', label='not')
    # plt.grid(True)
    # plt.legend(loc='best')
    # plt.savefig('../data/fig/年龄人才流失图')
    ###结论年龄在30到40之间人才流失度比较大
    # #绘制教育情况图
    # sns.countplot(x="Attrition", hue="Education", data=data)
    # plt.savefig('../data/fig/教育情况人才流失图')
    ###结论受教育程度是大学的人才流失度比较大
    # 5、分析离职和家庭和距离、环境满意度、工作投入和 职位等级 的关系
    # 1.离职和家庭和距离的关系
    # plt.subplot(221)
    # sns.boxplot(y="Attrition", x="DistanceFromHome", data=data)
    # # 2.离职和环境满意度之间的关系
    # plt.subplot(222)
    # sns.boxplot(y="Attrition", x="EnvironmentSatisfaction", data=data)
    # # 3.离职和工作投入的关系
    # plt.subplot(223)
    # sns.boxplot(y="Attrition", x="JobInvolvement", data=data)
    # # 4.离职和职位等级的关系
    # plt.subplot(224)
    # sns.boxplot(y="Attrition", x="JobLevel", data=data)
    # plt.show()
    # 结论：家庭距离越远的离职人数较多，而家庭距离较近的离职人数较少。
    # 环境满意度基本和离职率没有关系。
    # 工作投入越低的离职人数较多，而工作投入较高的离职人数较少。
    # 职位等级越低的离职人数较多，而职位等级的离职人数较少。
    # 5、分析离职和工作满意度、服务过几家公司、涨薪百分比和 绩效评分 的关系
    # 1.离职和工作满意度的关系
    # plt.subplot(221)
    # sns.boxplot(x="Attrition", y="JobSatisfaction", data=data)
    # 2.离职和服务过几家公司之间的关系
    # plt.subplot(222)
    # sns.boxplot(y="Attrition", x="NumCompaniesWorked", data=data)
    # # 3.离职和涨薪百分比的关系
    # plt.subplot(223)
    # sns.boxplot(y="Attrition", x="PercentSalaryHike", data=data)
    # # 4.离职和绩效评分的关系
    # plt.subplot(224)
    # sns.boxplot(y="Attrition", x="PerformanceRating", data=data)
    # plt.show()
    # 结论：工作满意度越低的离职人数较多，而工作满意度较高的离职人数较少。
    # 服务过几家公司基本和离职率没有关系。
    # 涨薪百分比越高的离职人数较多，而涨薪百分比较低的离职人数较少。
    # 绩效评分基本和离职率没有关系。。
    # 5、分析离职和关系满意度、股权等级、总工作年限和 上一年培训次数 的关系
    # 1.离职和关系满意度的关系
    # plt.subplot(221)
    # sns.boxplot(y="Attrition", x="RelationshipSatisfaction", data=data)
    # # 2.离职和股权等级之间的关系
    # plt.subplot(222)
    # sns.boxplot(y="Attrition", x="StockOptionLevel", data=data)
    # # 3.离职和总工作年限的关系
    # plt.subplot(223)
    # sns.boxplot(y="Attrition", x="TotalWorkingYears", data=data)
    # # 4.离职和上一年培训次数的关系
    # plt.subplot(224)
    # sns.boxplot(y="Attrition", x="TrainingTimesLastYear", data=data)
    # plt.show()
    # 结论
    # 关系满意度基本和离职率没有关系
    # 股权等级越低离职率越高
    # 总工作年限越低离职率越高，
    # 上一年培训次数基本和离职率没有关系
    # 5、分析离职和工作生活平衡度、工作时长、当前岗位在职时长和 上次升职时间 的关系
    # 1.离职和作生活平衡度的关系
    # plt.subplot(221)
    # sns.boxplot(y="Attrition", x="WorkLifeBalance", data=data)
    # # 2.离职和工作时长之间的关系
    # plt.subplot(222)
    # sns.boxplot(y="Attrition", x="YearsAtCompany", data=data)
    # # 3.离职和当前岗位在职时长的关系
    # plt.subplot(223)
    # sns.boxplot(y="Attrition", x="YearsInCurrentRole", data=data)
    # # 4.离职和上次升职时间的关系
    # plt.subplot(224)
    # sns.boxplot(y="Attrition", x="YearsSinceLastPromotion", data=data)
    # plt.show()
    # 结论
    # 工作生活平衡度越低离职率越高
    # 工作时长越长或者越短离职率越高，工作时长中等的离职率低
    # 当前岗位在职时间越长或者越短离职率越高，当前岗位在职时间离职率低
    # 上次升职时间和离职率基本没有关系


def feture_engineering(train_data, test_data, logger):
    """
    对给定的数据进行特征工程，提取出关键的特征
    :param data:数据源
    :param logger:日志
    :return:
        无用特征：
            EmployeeNumber：员工编号
            NumCompaniesWorked：服务过几家公司
            PerformanceRating：绩效评分
            StandardHours：标准工时
            RelationshipSatisfaction：关系满意度
            TrainingTimesLastYear：上一年培训次数
            YearsSinceLastPromotion：上次升职时间
            YearsWithCurrManager：和现任经理时长
            Over18：年龄超过18
        需要编码特征：
            BusinessTravel : ['Travel_Rarely' 'Travel_Frequently' 'Non-Travel']
            Department : ['Research & Development' 'Sales' 'Human Resources']
            EducationField : ['Life Sciences' 'Medical' 'Other' 'Technical Degree' 'Human Resources''Marketing']
            Gender : ['Male' 'Female']
            JobRole : ['Manufacturing Director' 'Laboratory Technician' 'Sales Executive'
                        'Research Scientist' 'Healthcare Representative' 'Human Resources'
                            'Sales Representative' 'Research Director' 'Manager']
            MaritalStatus : ['Divorced' 'Single' 'Married']
            OverTime : ['No' 'Yes']
    """

    logger.info("===============开始进行特征工程处理===============")
    print("===============开始进行特征工程处理===============")
    train_data = train_data.drop(['Over18', 'StandardHours', 'EmployeeNumber'], axis=1)
    test_data = test_data.drop(['Over18', 'StandardHours', 'EmployeeNumber'], axis=1)
    X_train = train_data.drop('Attrition', axis=1)
    X_test = test_data.drop('Attrition', axis=1)
    y_train = train_data['Attrition']
    y_test = test_data['Attrition']

    # 1.特征编码
    def fit_transform_features(df):
        # 无序特征用独热编码
        def encode_categorical(df):
            nominal_features = ['BusinessTravel', 'Department', 'EducationField',
                                'Gender', 'JobRole', 'MaritalStatus', 'OverTime']
            encoder = OneHotEncoder(handle_unknown='ignore', sparse_output=False)
            encoded = encoder.fit_transform(df[nominal_features])
            encoded_df = pd.DataFrame(encoded, columns=encoder.get_feature_names_out(nominal_features), index=df.index)
            df = df.drop(nominal_features, axis=1)
            df = pd.concat([df, encoded_df], axis=1)
            return df, encoder

        def handle_skewness(df):
            numeric_cols = df.select_dtypes(include=['float64', 'int64']).columns
            skewed_features = []
            for col in numeric_cols:
                skewness = df[col].skew()
                if abs(skewness) > 0.5:  # 设定偏态阈值
                    df[col] = np.log1p(df[col])
                    skewed_features.append(col)
            print(f"进行对数变换的特征: {skewed_features}")
            return df, skewed_features
            # 编码类别特征

        df, encoder = encode_categorical(df)
        # 处理偏态分布
        df, skewed_features = handle_skewness(df)
        return df, encoder, skewed_features

    def transform_features(df, encoder, skewed_features):
        # 编码类别特征
        nominal_features = ['BusinessTravel', 'Department', 'EducationField', 'Gender', 'JobRole', 'MaritalStatus',
                            'OverTime']
        encoded = encoder.transform(df[nominal_features])
        encoded_df = pd.DataFrame(encoded, columns=encoder.get_feature_names_out(nominal_features), index=df.index)
        df = df.drop(nominal_features, axis=1)
        df = pd.concat([df, encoded_df], axis=1)
        # 处理偏态分布
        for col in skewed_features:
            df[col] = np.log1p(df[col])
        return df

    # 标准化
    def fit_scale_features(df):
        numeric_cols = df.select_dtypes(include=['float64', 'int64']).columns
        scaler = StandardScaler()
        df[numeric_cols] = scaler.fit_transform(df[numeric_cols])
        return df, scaler, numeric_cols

    def scale_features(df, scaler, numeric_cols):
        df[numeric_cols] = scaler.transform(df[numeric_cols])
        return df

    # 处理样本不均
    def handle_imbalance(X, y):
        smote = SMOTE(random_state=42)
        X_balanced, y_balanced = smote.fit_resample(X, y)
        return X_balanced, y_balanced

    # 特征选择
    def select_features_cv(X, y, step=1, cv=5):
        estimator = LogisticRegression(max_iter=1000, random_state=42)
        rfecv = RFECV(
            estimator=estimator,
            step=step,
            cv=StratifiedKFold(cv),
            scoring='accuracy',
            n_jobs=-1
        )
        X_selected = rfecv.fit_transform(X, y)
        print(X_selected)
        selected_features = X.columns[rfecv.support_]
        return X_selected, selected_features, rfecv

    # 在训练集上进行特征转换
    # 1. 特征编码和处理偏态分布
    X_train, encoder, skewed_features = fit_transform_features(X_train)

    # 2. 标准化数值特征
    X_train, scaler, numeric_cols = fit_scale_features(X_train)

    # 3. 处理类别不平衡（SMOTE）
    X_train_balanced, y_train_balanced = handle_imbalance(X_train, y_train)

    # 在测试集上应用相同的转换
    # 1. 特征编码和处理偏态分布
    X_test = transform_features(X_test, encoder, skewed_features)

    # 2. 标准化数值特征
    X_test = scale_features(X_test, scaler, numeric_cols)

    # 打印数据集
    print(f'训练特征：{X_train_balanced}，\n训练标签{y_train_balanced}，\n预测特征{X_test}，\n真实标签{y_test}')
    return X_train_balanced, y_train_balanced, X_test, y_test


def model_train(x_train, y_train, x_test, y_test, logger):
    """
    1.数据集切分
    2.网格化搜索与交叉验证
    3.模型实例化
    4.模型训练
    5.模型评价
    6.模型保存
    :param data:特征工程后的数据集
    :param features:特征名称
    :param logger:日志对象
    :return:
    """
    logger.info("===============开始训练模型===============")
    # 2.网格化搜索与交叉验证
    # 2.1备选的超参数
    # print("开始网格化搜索")
    # print(datetime.datetime.now())  # 2024-11-26 15:38:26.898828
    # param_dict = {
    #     'n_estimators': [60, 100, 150, 190, 210, 200, 250, ],
    #     'max_depth': [3, 4, 5, 6, 7, 8, 9],
    #     'learning_rate': [0.1, 0.01]
    # }
    # # 2.2实例化网格化搜索，配置交叉验证
    # grid_cv = GridSearchCV(estimator=XGBRegressor(),
    #                        param_grid=param_dict, cv=5)
    # # 2.3网格化搜索与交叉验证训练
    # grid_cv.fit(x_train, y_train)
    # # 2.4输出最优的超参数组合
    # print(grid_cv.best_params_)  # {'learning_rate': 0.1, 'max_depth': 6, 'n_estimators':250}
    # print("结束网格化搜索")
    # print(datetime.datetime.now())  # 2025.08.06
    # # logger.info("网格化搜索后找到的最优的超参数组合是：learning_rate: 0.1, max_depth: 6, n_estimators: 250")

    ##-----
    ## 使用XGBoost进行预测
    # 初始化 XGBoost 模型
    model = XGBClassifier(
        n_estimators=250,  # 树的数量
        learning_rate=0.05,  # 学习率
        max_depth=3,  # 树的最大深度
        random_state=42,  # 随机种子
        use_label_encoder=False,  # 新版本中不需要 label encoder
        eval_metric='logloss',  # 评估指标
        reg_alpha=1,  # L2 正则化
        reg_lambda=1,  # L2正则化
        subsample=0.8,  # 使用80%样本建树
        colsample_bytree=0.8,  # 使用80%特征建树
        gamma=0.1  # 分裂最小损失
    )

    # 在训练集上训练模型
    model.fit(x_train, y_train)
    logger.info("========模型训练完毕=============")

    # 使用训练好的模型在测试集上进行预测
    y_pred = model.predict(x_test)

    # 打印分类报告
    print(classification_report(y_test, y_pred))

    # 计算混淆矩阵
    cm = confusion_matrix(y_test, y_pred)
    print("混淆矩阵：\n", cm)
    # ##----

    # 计算 ROC AUC 分数
    y_pred_proba = model.predict_proba(x_test)[:, 1]
    roc_auc = roc_auc_score(y_test, y_pred_proba)
    print(f"ROC AUC Score: {roc_auc:.4f}")
    logger.info(f"auc分为：{roc_auc}")
    # 检测模型是否过拟合
    train_pred = model.predict(x_train)
    train_acc = accuracy_score(y_train, train_pred)
    test_acc = accuracy_score(y_test, y_pred)
    print(f"训练集准确率: {train_acc:.4f}")
    print(f"测试集准确率: {test_acc:.4f}")

    # 划分数据集
    # # X=data.drop('Attrition',axis=1)
    # X = data.drop(['Attrition', 'YearsAtCompany', 'YearsInCurrentRole', 'WorkLifeBalance', 'MaritalStatus',
    #                'EnvironmentSatisfaction', 'JobSatisfaction'], axis=1)
    # Y = data['Attrition']
    # print(X.columns.tolist())
    # x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, random_state=20)

    # 2.网格化搜索与交叉验证
    # 2.1备选的超参数
    # print("开始网格化搜索")
    # print(datetime.datetime.now())  # 2024-11-26 15:38:26.898828
    # param_dict = {
    #     'n_estimators': [50, 100, 150, 200],
    #     'max_depth': [3, 6, 9],
    #     'learning_rate': [0.1, 0.01]
    # }
    # # 2.2实例化网格化搜索，配置交叉验证
    # grid_cv = GridSearchCV(estimator=XGBRegressor(),
    #                        param_grid=param_dict, cv=5)
    # # 2.3网格化搜索与交叉验证训练
    # grid_cv.fit(x_train, y_train)
    # # 2.4输出最优的超参数组合
    # print(grid_cv.best_params_)  # {'learning_rate': 0.1, 'max_depth': 3, 'n_estimators': 50}
    # print("结束网格化搜索")
    # print(datetime.datetime.now())  # 2025.08.06
    # # logger.info("网格化搜索后找到的最优的超参数组合是：learning_rate: 0.1, max_depth: 3, n_estimators: 50")
    # xgb模型训练
    # xgb=XGBClassifier(
    #     n_estimators=50,
    #     learning_rate=0.1,
    #     max_depth=3,
    #     random_state=30,
    #     eval_metric='logloss'
    # )
    # #训练模型
    # xgb.fit(x_train,y_train)
    # #测试模型
    # y_pred=xgb.predict(x_test)
    # #打印报告
    # print(classification_report(y_test,y_pred))
    # #计算ROC AUC 分数
    # y_pred_proba =xgb.predict_proba(x_test)[:,1]
    # roc_auc=roc_auc_score(y_test,y_pred_proba)
    # print(f"ROC AUC Score:{roc_auc}")
    # logger.info(f"auc分数为{roc_auc}")
    # #模型保存
    # joblib.dump(xgb, '../model/xgb.pkl')

    # 决策树模型训练
    # params = {
    #     "criterion": ("gini", "entropy"),
    #     "splitter": ("best", "random"),
    #     "max_depth": (list(range(1, 20))),
    #     "min_samples_split": [2, 3, 4],
    #     "min_samples_leaf": list(range(1, 20)),
    # }
    # tree_clf = DecisionTreeRegressor(random_state=42)
    # tree_cv = GridSearchCV(
    #     tree_clf,
    #     params,
    #     scoring="f1",
    #     n_jobs=-1,
    #     verbose=1,
    #     cv=5
    # )
    # tree_cv.fit(x_train, y_train)
    # best_params = tree_cv.best_params_
    # print(f"最优超参数为：{best_params}")
    # logger.info(f"最优超参数为：{best_params}")
    # 最优超参数为：{'criterion': 'entropy', 'max_depth': 11, 'min_samples_leaf': 1, 'min_samples_split': 2, 'splitter': 'random'}
    # # 创建模型
    # tree_cla_model = DecisionTreeClassifier(criterion='entropy', max_depth=11, min_samples_leaf=1,
    #                                         min_samples_split=2, splitter='random', random_state=20)
    # # 训练模型
    # tree_cla_model.fit(x_train, y_train)
    # # 预测
    # y_pred = tree_cla_model.predict(x_test)
    # # 计算ROC AUC 分数
    # y_pred_proba = tree_cla_model.predict_proba(x_test)[:, 1]
    # roc_auc = roc_auc_score(y_test, y_pred_proba)
    # print(f"ROC AUC Score:{roc_auc}")
    # logger.info(f"决策树分类，auc分数为：{roc_auc}")
    # 决策树分类不适合：ROC AUC Score:0.6038053490883679

    # 3.逻辑回归模型训练
    # # 3.1创建模型
    # log_reg=LogisticRegression(max_iter=1000,random_state=42)
    # #3.2训练模型
    # log_reg.fit(x_train,y_train)
    # #3.3测试模型
    # y_pred=log_reg.predict(x_test)
    # #3.4打印评估报告
    # print(classification_report(y_test,y_pred))
    # logger.info(f"模型的评估报告为：{classification_report(y_test,y_pred)}")
    # #3.5计算混淆矩阵
    # cm=confusion_matrix(y_test,y_pred)
    # print("混淆矩阵：\n",cm)
    # #4计算ROC AUC分
    # y_pred_proba=log_reg.predict_proba(x_test)[:,1]
    # roc_auc=roc_auc_score(y_test,y_pred_proba)
    # print(f"ROC AUC Score:{roc_auc}")
    # logger.info(f"auc分数为：{roc_auc}")
    #
    # # 5.模型保存
    # joblib.dump(log_reg, '../model/log_reg.pkl')


# def model_pred(model, data, logger):
#     # 加载数据
#     pred_test_data = data
#     y_true = pred_test_data['Attrition']
#     x_true = pred_test_data.drop(['Attrition', 'YearsAtCompany', 'YearsInCurrentRole', 'WorkLifeBalance',
#                                   'MaritalStatus', 'EnvironmentSatisfaction', 'JobSatisfaction'], axis=1)
#     print(x_true.columns.tolist())  # 打印所有列名
#
#     # 加载模型。进行预测
#     model = model
#     y_pred = model.predict(x_true)
#     # 模型计算auc分
#     y_pred_proba = model.predict_proba(x_true)[:, 1]
#     roc_auc = roc_auc_score(y_true, y_pred_proba)
#     print(f"ROC AUC Score:{roc_auc}")
#     logger.info(f"auc分数为：{roc_auc}")
#     # 检测模型是否过拟合
#     train_pred = model.predict(x_train)
#     train_acc = accuracy_score(y_train, train_pred)
#     test_acc = accuracy_score(y_test, y_pred)
#     print(f"训练集准确率: {train_acc:.4f}")
#     print(f"测试集准确率: {test_acc:.4f}")


class PowerLoadModel(object):
    def __init__(self, filename):
        # 配置日志记录
        logfile_name = "train_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S')
        self.logfile = Logger('../', logfile_name).get_logger()
        # 获取数据源
        self.data_source = filename


if __name__ == '__main__':
    # # 1.加载数据集
    input_file = os.path.join('../data/train.csv', 'train.csv')  # 拼接文件
    model = PowerLoadModel(input_file)
    data = pd.read_csv('../data/train.csv')
    data_test = pd.read_csv('../data/test.csv')
    # # 2.分析数据
    analysis_data(model.data_source)
    # 3.特征工程
    x_train, y_train, x_test, y_test = feture_engineering(data, data_test, model.logfile)
    # 4.模型训练、模型评估与模型保存
    model_train(x_train, y_train, x_test, y_test, model.logfile)

